Skip to main content

copp\copp\copp3\opt3/
copp3_socp.rs

1//! 3rd-order Convex-Objective Path Parameterization (COPP3) based on second-order cone programming (SOCP).
2//!
3//! # Method identity
4//! This module implements the **optimization backend** for COPP3 by transforming
5//! third-order path-parameterization constraints/objectives into Clarabel-compatible
6//! conic form and solving with SOCP.
7//!
8//! # Discrete variables (local notation)
9//! On a path grid `s[0..=n]`:
10//! - `a[k]` denotes $\dot{s}_k^2$;
11//! - `b[k]` denotes $\ddot{s}_k$;
12//! - decision vector starts with `x = [a[0..=n], b[0..=n], x_others]`, where
13//!   `x_others` are auxiliary variables introduced by objectives ([`Time`](crate::prelude::CoppObjective::Time),
14//!   [`ThermalEnergy`](crate::prelude::CoppObjective::ThermalEnergy), [`TotalVariationTorque`](crate::prelude::CoppObjective::TotalVariationTorque), [`Linear`](crate::prelude::CoppObjective::Linear)).
15//!
16//! # High-level pipeline
17//! 1. Validate interval/boundary/objective contract.
18//! 2. Assemble standard TOPP3 conic constraints.
19//! 3. Add COPP3 objective-induced variables/cones.
20//! 4. Build sparse matrices `A`, `P`, vector `q`, and solve by Clarabel.
21//! 5. Apply status acceptance policy ([`ClarabelOptions::is_allow`](crate::solver::copp2_socp::ClarabelOptions::is_allow)) and extract
22//!    `(a,b)` only when accepted.
23//!
24//! # API layering
25//! - [`copp3_socp`](crate::solver::copp3_socp::copp3_socp): strict/normal API, returns only accepted [`Topp3Profile`](crate::solver::copp3_socp::Topp3Profile).
26//! - [`copp3_socp_expert`](crate::solver::copp3_socp::copp3_socp_expert): expert API returning `(Option<Topp3Profile>, DefaultSolution<f64>)`.
27//! - [`copp3_socp_expert_with_info`](crate::solver::copp3_socp::copp3_socp_expert_with_info): expert API plus Clarabel linear-solver
28//!   metadata for wrappers that need solver-side diagnostics.
29
30use crate::copp::clarabel_backend::{ConstraintsClarabel, ObjConsClarabel};
31use crate::copp::copp3::Topp3Profile;
32#[cfg(any(feature = "c", feature = "python", test))]
33use crate::copp::copp3::Topp3ProfileRef;
34use crate::copp::copp3::formulation::{Copp3Problem, get_weight_a_copp3, get_weight_a_topp3};
35use crate::copp::copp3::opt3::ClarabelExpertInfor3rd;
36use crate::copp::copp3::opt3::clarabel_constraints::{
37    clarabel_standard_capacity_topp3, clarabel_standard_constraint_topp3,
38};
39use crate::copp::{
40    ClarabelOptions, CoppObjective, clarabel_to_copp3_solution, validate_copp3_objectives,
41};
42use crate::diag::{
43    CoppError, DebugVerboser, SilentVerboser, SummaryVerboser, TraceVerboser, Verboser, Verbosity,
44    check_boundary_state_copp3_valid, check_s_interval_valid, format_duration_human,
45};
46use crate::robot::robot_core::{Robot, RobotBasic, RobotTorque};
47use clarabel::algebra::CscMatrix;
48use clarabel::solver::SupportedConeT::{NonnegativeConeT, SecondOrderConeT};
49use clarabel::solver::{DefaultSolution, DefaultSolver, IPSolver, SupportedConeT};
50use core::f64;
51use itertools::{Itertools, izip};
52use nalgebra::{DMatrix, DVectorView};
53
54/// Strict COPP3-SOCP API for production use.
55///
56/// # Purpose
57/// Use this entry when caller only needs a valid [`Topp3Profile`](crate::solver::copp3_socp::Topp3Profile) and treats
58/// non-accepted solver statuses as hard failures.
59///
60/// # Contract
61/// - Internally calls [`copp3_socp_expert`](crate::solver::copp3_socp::copp3_socp_expert).
62/// - Returns `Ok(Topp3Profile { .. })` **iff** `options.is_allow(solution.status)` is `true`.
63/// - Returns [`Err(CoppError::ClarabelSolverStatus(...))`](CoppError::ClarabelSolverStatus) when status is not accepted.
64///
65/// # Returns
66/// Returns accepted COPP3 profile.
67///
68/// # Errors
69/// Returns [`CoppError`](crate::diag::CoppError) on model/solver failures and non-accepted solver status.
70///
71/// # Notes
72/// For workflows requiring low-level diagnostics (`status` and raw Clarabel solution fields),
73/// prefer [`copp3_socp_expert`](crate::solver::copp3_socp::copp3_socp_expert).
74pub fn copp3_socp<'a, M: RobotTorque>(
75    problem: &Copp3Problem<'a, M>,
76    options: &ClarabelOptions,
77) -> Result<Topp3Profile, CoppError> {
78    let (result, solution) = copp3_socp_expert(problem, options)?;
79    result.ok_or_else(|| CoppError::ClarabelSolverStatus("copp3_socp".into(), solution.status))
80}
81
82/// Expert COPP3-SOCP API with full Clarabel solution exposure.
83///
84/// # Return contract
85/// - `Ok((Some(result), solution))`: status accepted by `options.is_allow(solution.status)`.
86/// - `Ok((None, solution))`: solve finished but status not accepted.
87/// - `Err(...)`: input/model/solver-construction runtime failures.
88///
89/// # Returns
90/// Returns tuple `(Option<Topp3Profile>, DefaultSolution<f64>)` for diagnostic use.
91///
92/// # Errors
93/// Returns [`CoppError`](crate::diag::CoppError) only for true runtime failures.
94///
95/// # Contract
96/// - caller must handle `None` profile for non-accepted statuses;
97/// - status acceptance policy is defined by `options.is_allow`.
98///   See [`ClarabelOptions::is_allow`](crate::solver::copp3_socp::ClarabelOptions::is_allow)
99///   for a status-handling example.
100///
101/// # Verbosity behavior
102/// Logging is layered by `options.verbosity()`:
103/// - [`Silent`](Verbosity::Silent): no algorithm logs;
104/// - [`Summary`](Verbosity::Summary): lifecycle milestones and elapsed time;
105/// - [`Debug`](Verbosity::Debug): assembly-level counters and stage summaries;
106/// - [`Trace`](Verbosity::Trace): fine-grained stage deltas and solver snapshot diagnostics.
107pub fn copp3_socp_expert<'a, M: RobotTorque>(
108    problem: &Copp3Problem<'a, M>,
109    options: &ClarabelOptions,
110) -> Result<(Option<Topp3Profile>, DefaultSolution<f64>), CoppError> {
111    let info = copp3_socp_expert_with_info(problem, options)?;
112    let _ = &info.linsolver;
113    Ok((info.result, info.solution))
114}
115
116/// Expert COPP3-SOCP API with Clarabel solution and linear-solver diagnostics.
117///
118/// Use this variant when callers need more than
119/// [`DefaultSolution`](clarabel::solver::DefaultSolution), because Clarabel stores linear-solver metadata on the
120/// solver `info` object rather than inside the returned solution.
121///
122/// Status acceptance follows
123/// [`ClarabelOptions::is_allow`](crate::solver::copp3_socp::ClarabelOptions::is_allow);
124/// see that method for the shared status-handling pattern.
125pub fn copp3_socp_expert_with_info<'a, M: RobotTorque>(
126    problem: &Copp3Problem<'a, M>,
127    options: &ClarabelOptions,
128) -> Result<ClarabelExpertInfor3rd, CoppError> {
129    match options.verbosity() {
130        Verbosity::Silent => copp3_socp_core(problem, (options, SilentVerboser)),
131        Verbosity::Summary => copp3_socp_core(problem, (options, SummaryVerboser::new())),
132        Verbosity::Debug => copp3_socp_core(problem, (options, DebugVerboser::new())),
133        Verbosity::Trace => copp3_socp_core(problem, (options, TraceVerboser::new())),
134    }
135}
136
137/// Core implementation for COPP3-SOCP expert flow.
138///
139/// # Internal contract
140/// `options_verboser` packs:
141/// - `options`: acceptance policy and Clarabel numerical settings;
142/// - `verboser`: concrete logger implementation chosen by external verbosity dispatch.
143///
144/// # Invariants
145/// - decision-variable layout always starts with contiguous `a[0..=n]` and `b[0..=n]`;
146/// - `q_object.len()` is treated as final `n_var` before solver build;
147/// - extracted `(a,b)` is produced only through [`clarabel_to_copp3_solution`](crate::solver::copp3_socp::clarabel_to_copp3_solution) when status is accepted.
148fn copp3_socp_core<'a, M: RobotTorque>(
149    problem: &Copp3Problem<'a, M>,
150    options_verboser: (&ClarabelOptions, impl Verboser),
151) -> Result<ClarabelExpertInfor3rd, CoppError> {
152    let (options, mut verboser) = options_verboser;
153    let idx_s_start = problem.idx_s_start;
154    let a_boundary = problem.a_boundary;
155    let b_boundary = problem.b_boundary;
156    let num_stationary = problem.num_stationary;
157    if verboser.is_enabled(Verbosity::Summary) {
158        verboser.record_start_time();
159    }
160    if verboser.is_enabled(Verbosity::Trace) {
161        let settings = options.clarabel_settings();
162        crate::verbosity_log!(
163            crate::diag::Verbosity::Summary,
164            "copp3_socp: options snapshot -> allow(almost={}, max_iter={}, max_time={}, callback_term={}, insufficient_progress={}), tol_gap_rel={}, tol_feas={}, max_iter={}, verbose={}",
165            options.is_allow(clarabel::solver::SolverStatus::AlmostSolved),
166            options.is_allow(clarabel::solver::SolverStatus::MaxIterations),
167            options.is_allow(clarabel::solver::SolverStatus::MaxTime),
168            options.is_allow(clarabel::solver::SolverStatus::CallbackTerminated),
169            options.is_allow(clarabel::solver::SolverStatus::InsufficientProgress),
170            settings.tol_gap_rel,
171            settings.tol_feas,
172            settings.max_iter,
173            settings.verbose
174        );
175    }
176    // Check input validity
177    check_boundary_state_copp3_valid(a_boundary, b_boundary)?;
178    let n = problem.a_linearization.len() - 1;
179    let idx_s_final = idx_s_start + n;
180    if verboser.is_enabled(Verbosity::Summary) {
181        crate::verbosity_log!(
182            crate::diag::Verbosity::Summary,
183            "\ncopp3_socp started: {} <= idx_s <= {}, objectives = {}, s_len = {}.",
184            idx_s_start,
185            idx_s_final,
186            problem.objectives.len(),
187            problem.a_linearization.len()
188        );
189    }
190    check_s_interval_valid("copp3_socp", idx_s_start, idx_s_final)?;
191    validate_copp3_objectives(
192        "copp3_socp",
193        problem.objectives,
194        problem.robot.dim(),
195        problem.a_linearization.len(),
196    )?;
197    // Let x = [a[0,1,...,n],
198    //          b[0,1,...,n],
199    //          xi[0,1,...,len_xi-1], (if xi exists.)
200    //          x_others] \in R^{2*(n+1), x_others}.
201    // Step 1. Deal with constraints
202    // Step 1.1 Compute the number of constraints
203    let (cap_val_std, cap_b_std, cap_cone_std) =
204        clarabel_standard_capacity_topp3(&problem.robot.constraints, (idx_s_start, idx_s_final));
205    let (cap_val_obj, cap_b_obj, cap_cone_obj, n_vars) =
206        clarabel_objective_capacity_copp3(n, problem.objectives, problem.robot);
207    if verboser.is_enabled(Verbosity::Debug) {
208        crate::verbosity_log!(
209            crate::diag::Verbosity::Summary,
210            "copp3_socp: capacity estimate std(val={cap_val_std}, b={cap_b_std}, cone={cap_cone_std}), obj(val={cap_val_obj}, b={cap_b_obj}, cone={cap_cone_obj}), n_vars={n_vars}."
211        );
212    }
213    // s=b-A*x \in cone, where A[row[i],col[i]]=val[i], A \in R^{m*(n+1)}, b \in R^m, s \in R^m
214    // -s=-b+A*x
215    let mut row = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
216    let mut col = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
217    let mut val = Vec::<f64>::with_capacity(cap_val_std + cap_val_obj);
218    let mut b = Vec::<f64>::with_capacity(cap_b_std + cap_b_obj);
219    let mut cones = Vec::<SupportedConeT<f64>>::with_capacity(cap_cone_std + cap_cone_obj);
220    if verboser.is_enabled(Verbosity::Trace) {
221        crate::verbosity_log!(
222            crate::diag::Verbosity::Summary,
223            "copp3_socp: allocated capacities row/col/val/b/cones <= {}/{}/{}/{}/{}",
224            cap_val_std + cap_val_obj,
225            cap_val_std + cap_val_obj,
226            cap_val_std + cap_val_obj,
227            cap_b_std + cap_b_obj,
228            cap_cone_std + cap_cone_obj
229        );
230    }
231    // Step 1.2 set constraints of the standard topp3-lp problem
232    let s = problem
233        .robot
234        .constraints
235        .s_vec(idx_s_start, idx_s_final + 1)?;
236    let row_before_std = row.len();
237    let col_before_std = col.len();
238    let val_before_std = val.len();
239    let b_before_std = b.len();
240    let cones_before_std = cones.len();
241    clarabel_standard_constraint_topp3(
242        &problem.as_topp3_problem(),
243        &s,
244        (&mut row, &mut col, &mut val, &mut b, &mut cones),
245        num_stationary,
246        &verboser,
247    )?;
248    if verboser.is_enabled(Verbosity::Trace) {
249        crate::verbosity_log!(
250            crate::diag::Verbosity::Summary,
251            "copp3_socp: standard-constraints delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}",
252            row.len() - row_before_std,
253            col.len() - col_before_std,
254            val.len() - val_before_std,
255            b.len() - b_before_std,
256            cones.len() - cones_before_std
257        );
258    }
259    // Step 2. set objective
260    // Step 2.1. determine whether xi=sqrt(a) is needed.
261    let row_before_sqrt = row.len();
262    let col_before_sqrt = col.len();
263    let val_before_sqrt = val.len();
264    let b_before_sqrt = b.len();
265    let cones_before_sqrt = cones.len();
266    let n_var_old = clarabel_sqrt_a_copp3(
267        n,
268        problem.objectives,
269        (&mut row, &mut col, &mut val, &mut b, &mut cones),
270        num_stationary,
271    );
272    if verboser.is_enabled(Verbosity::Trace) {
273        crate::verbosity_log!(
274            crate::diag::Verbosity::Summary,
275            "copp3_socp: sqrt-a stage delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}, n_var_old={}",
276            row.len() - row_before_sqrt,
277            col.len() - col_before_sqrt,
278            val.len() - val_before_sqrt,
279            b.len() - b_before_sqrt,
280            cones.len() - cones_before_sqrt,
281            n_var_old
282        );
283    }
284    let mut q_object = Vec::<f64>::with_capacity(n_vars);
285    q_object.resize(n_var_old, 0.0);
286    // Step 2.2. add constraints and objective for each term in the objective.
287    let row_before_obj = row.len();
288    let col_before_obj = col.len();
289    let val_before_obj = val.len();
290    let b_before_obj = b.len();
291    let cones_before_obj = cones.len();
292    let q_before_obj = q_object.len();
293    clarabel_objective_copp3(
294        problem,
295        num_stationary,
296        (
297            &mut row,
298            &mut col,
299            &mut val,
300            &mut b,
301            &mut cones,
302            &mut q_object,
303        ),
304    )?;
305    if verboser.is_enabled(Verbosity::Trace) {
306        let (q_min, q_max) = q_object
307            .iter()
308            .fold((f64::INFINITY, f64::NEG_INFINITY), |(mn, mx), &v| {
309                (mn.min(v), mx.max(v))
310            });
311        crate::verbosity_log!(
312            crate::diag::Verbosity::Summary,
313            "copp3_socp: objective stage delta row/col/val/b/cones/q = +{}/+{}/+{}/+{}/+{}/+{}, q_range=[{}, {}]",
314            row.len() - row_before_obj,
315            col.len() - col_before_obj,
316            val.len() - val_before_obj,
317            b.len() - b_before_obj,
318            cones.len() - cones_before_obj,
319            q_object.len() - q_before_obj,
320            q_min,
321            q_max
322        );
323    }
324    if verboser.is_enabled(Verbosity::Debug) {
325        crate::verbosity_log!(
326            crate::diag::Verbosity::Summary,
327            "copp3_socp: after objective assembly row={}, col={}, val={}, b={}, cones={}, q={}",
328            row.len(),
329            col.len(),
330            val.len(),
331            b.len(),
332            cones.len(),
333            q_object.len()
334        );
335    }
336    // Step 2.3 build the constraints
337    let n_var = q_object.len();
338    let row_len = row.len();
339    let col_len = col.len();
340    let val_len = val.len();
341    let b_len = b.len();
342    let cones_len = cones.len();
343    let a_csc = CscMatrix::new_from_triplets(b.len(), n_var, row, col, val);
344    let p_object = CscMatrix::<f64>::zeros((n_var, n_var));
345    if verboser.is_enabled(Verbosity::Trace) {
346        crate::verbosity_log!(
347            crate::diag::Verbosity::Summary,
348            "copp3_socp: matrix built with m={}, n={}, A.nnz={}, P.nnz={}",
349            b.len(),
350            n_var,
351            a_csc.nnz(),
352            p_object.nnz()
353        );
354    }
355    if verboser.is_enabled(Verbosity::Summary) {
356        crate::verbosity_log!(
357            crate::diag::Verbosity::Summary,
358            "copp3_socp: ready to solve with row/col/val/b/cones = {row_len}/{col_len}/{val_len}/{b_len}/{cones_len} and n_var = {n_var}.",
359        );
360    }
361    // Step 3. solve the SOCP problem
362    let settings = options.clarabel_settings().clone();
363    let mut solver = DefaultSolver::<f64>::new(&p_object, &q_object, &a_csc, &b, &cones, settings)
364        .map_err(|e| CoppError::ClarabelSolverError("copp3_socp".into(), e))?;
365    solver.solve();
366    let linsolver = solver.info.linsolver.clone();
367    let solution = solver.solution;
368    if verboser.is_enabled(Verbosity::Summary) {
369        crate::verbosity_log!(
370            crate::diag::Verbosity::Summary,
371            "copp3_socp: solve done, status = {:?}, elapsed = {}.",
372            solution.status,
373            format_duration_human(verboser.elapsed())
374        );
375    }
376    if verboser.is_enabled(Verbosity::Trace) {
377        let show = solution.x.len().min(3);
378        crate::verbosity_log!(
379            crate::diag::Verbosity::Summary,
380            "copp3_socp: solution x_len={}, head={:?}",
381            solution.x.len(),
382            &solution.x[0..show]
383        );
384    }
385    let result = if options.is_allow(solution.status) {
386        Some(clarabel_to_copp3_solution(
387            &solution.x.as_slice()[0..2 * (n + 1)],
388            &s,
389            num_stationary,
390        ))
391    } else {
392        None
393    };
394    if verboser.is_enabled(Verbosity::Trace) {
395        crate::verbosity_log!(
396            crate::diag::Verbosity::Summary,
397            "copp3_socp: allow(status)={}, extracted_profile={}",
398            options.is_allow(solution.status),
399            if result.is_some() {
400                "Some(Topp3Profile)"
401            } else {
402                "None"
403            }
404        );
405    }
406    Ok(ClarabelExpertInfor3rd {
407        result,
408        solution,
409        linsolver,
410    })
411}
412
413/// Determine the length of xi[k] = sqrt(a[k + k_skip]) in the decision variable x.
414#[inline(always)]
415fn length_xi(n: usize, num_stationary: (usize, usize)) -> usize {
416    n + 1 - num_stationary.0.max(1) - num_stationary.1.max(1)
417}
418
419/// Return k_skip, where xi[k] = sqrt(a[k + k_skip])
420#[inline(always)]
421fn skip_a_for_xi(num_stationary_start: usize) -> usize {
422    num_stationary_start.max(1)
423}
424
425/// Add the constraints for sqrt(a) >= xi in COPP3 optimization.
426/// x = [a[0,...,n], b[0,...,n], xi[0,...,len_xi-1], ...] \in R^{2*(n+1)+len_xi+...}.
427/// sqrt(a[k]) >= xi[k] >= 0
428/// num_val <= 4*n, num_b <= 4*n, num_cones <= n
429/// Return the len of the new x: n+1 or 2*(n+1)
430fn clarabel_sqrt_a_copp3(
431    n: usize,
432    objective: &[CoppObjective],
433    constraints: ConstraintsClarabel,
434    num_stationary: (usize, usize),
435) -> usize {
436    let (row, col, val, b, cones) = constraints;
437    for obj in objective {
438        match obj {
439            CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _) => {
440                let n_skip = skip_a_for_xi(num_stationary.0);
441                let len_xi = length_xi(n, num_stationary); // < n
442                // xi >= 0
443                // A*x-b = -s = -1*xi[k] <= 0
444                row.extend(b.len()..b.len() + len_xi);
445                col.extend((2 * (n + 1))..(2 * (n + 1) + len_xi));
446                val.resize(val.len() + len_xi, -1.0);
447                b.resize(b.len() + len_xi, 0.0);
448                cones.push(NonnegativeConeT(len_xi));
449                // sqrt(a) >= xi
450                // xi^2 <= a
451                // xi^2 + (a - 0.25)^2 <= (a + 0.25)^2
452                // [a[k]+0.25, a[k]-0.25, xi[k]] \in SOC
453                // -A*x+b = s = [x[k+n_skip]+0.25, x[k+n_skip]-0.25, x[2*(n+1)+k]] \in SOC
454                row.extend(b.len()..b.len() + 3 * len_xi);
455                val.resize(val.len() + 3 * len_xi, -1.0);
456                cones.resize(cones.len() + len_xi, SecondOrderConeT(3));
457                for k in 0..len_xi {
458                    col.extend([k + n_skip, k + n_skip, 2 * (n + 1) + k]);
459                    b.extend([0.25, -0.25, 0.0]);
460                }
461                return 2 * (n + 1) + len_xi;
462            }
463            _ => {}
464        }
465    }
466    2 * (n + 1)
467}
468
469/// Determine the number of clarabel's capacity for the objective in COPP3.
470fn clarabel_objective_capacity_copp3<M: RobotBasic>(
471    n: usize,
472    objective: &[CoppObjective],
473    robot: &Robot<M>,
474) -> (usize, usize, usize, usize) {
475    // Step 1. sqrt(a[k]) >= xi[k] >= 0
476    // num_val <= 4*n, num_b <= 4*n, num_cones <= n, n_var <= n
477    let (mut capacity_val, mut capacity_b, mut capacity_cones, mut n_vars) =
478        if objective.iter().any(|obj| {
479            matches!(
480                obj,
481                CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
482            )
483        }) {
484            (4 * n, 4 * n, n, 2 * (n + 1))
485        } else {
486            (0, 0, 0, 2 * (n + 1))
487        };
488    // Step 2. objective function
489    let dim = robot.dim();
490    for obj in objective {
491        match obj {
492            CoppObjective::Time(_) => {
493                // num_val <= 5*n, num_b <= 4*n, num_cones <= n, n_var <= n
494                capacity_val += 5 * n;
495                capacity_b += 4 * n;
496                capacity_cones += n;
497                n_vars += n;
498            }
499            CoppObjective::ThermalEnergy(_, _) => {
500                // num_val <= (4+2*dim)*(n+1), num_b <= (2+dim)*(n+1), num_cones <= n+1, n_var <= n+1
501                capacity_val += (4 + 2 * dim) * (n + 1);
502                capacity_b += (dim + 2) * (n + 1);
503                capacity_cones += n + 1;
504                n_vars += n + 1;
505            }
506            CoppObjective::TotalVariationTorque(_, _) => {
507                // num_val <= 10*n*dim, num_b <= 2*n*dim, num_cones <= 1, n_var <= n*dim
508                capacity_val += 10 * dim * n;
509                capacity_b += 2 * dim * n;
510                capacity_cones += 1;
511                n_vars += dim * n;
512            }
513            _ => {}
514        }
515    }
516    (capacity_val, capacity_b, capacity_cones, n_vars)
517}
518
519fn clarabel_objective_copp3<M: RobotTorque>(
520    problem: &Copp3Problem<M>,
521    num_stationary: (usize, usize),
522    objective_constraints: ObjConsClarabel,
523) -> Result<(), CoppError> {
524    let (row, col, val, b, cones, q_object) = objective_constraints;
525    let n = problem.a_linearization.len() - 1;
526    let s = problem
527        .robot
528        .constraints
529        .s_vec(problem.idx_s_start, problem.idx_s_start + n + 1)?;
530    let weight_a_time = if problem
531        .objectives
532        .iter()
533        .any(|obj| matches!(obj, CoppObjective::Time(_)))
534    {
535        get_weight_a_topp3(&s, num_stationary)
536    } else {
537        vec![]
538    };
539    let weight_a_torque = if problem
540        .objectives
541        .iter()
542        .any(|obj| matches!(obj, CoppObjective::ThermalEnergy(_, _)))
543    {
544        get_weight_a_copp3(&s, num_stationary)
545    } else {
546        vec![]
547    };
548    let coeffs_torque = if problem.objectives.iter().any(|obj| {
549        matches!(
550            obj,
551            CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
552        )
553    }) {
554        // shape: (dim, n) since there are n+1 a and n b.
555        problem.robot.torque_coeff(problem.idx_s_start, n + 1)?
556    } else {
557        (
558            DMatrix::<f64>::zeros(0, 0),
559            DMatrix::<f64>::zeros(0, 0),
560            DMatrix::<f64>::zeros(0, 0),
561        )
562    };
563    for obj in problem.objectives {
564        match obj {
565            CoppObjective::Time(weight) => {
566                if !clarabel_objective_time_copp3(
567                    &s,
568                    *weight,
569                    &weight_a_time,
570                    num_stationary,
571                    (row, col, val, b, cones, q_object),
572                ) {
573                    return Err(CoppError::InvalidInput(
574                        "clarabel_objective_copp3".into(),
575                        "Invalid Time objective".into(),
576                    ));
577                }
578            }
579            CoppObjective::ThermalEnergy(weight, normalize) => {
580                if !clarabel_objective_thermal_energy_copp3(
581                    &weight_a_torque,
582                    *weight,
583                    normalize,
584                    &coeffs_torque,
585                    num_stationary,
586                    (row, col, val, b, cones, q_object),
587                ) {
588                    return Err(CoppError::InvalidInput(
589                        "clarabel_objective_copp3".into(),
590                        "Invalid ThermalEnergy objective".into(),
591                    ));
592                }
593            }
594            CoppObjective::TotalVariationTorque(weight, normalize) => {
595                if !clarabel_objective_tv_torque_copp3(
596                    *weight,
597                    normalize,
598                    &coeffs_torque,
599                    num_stationary,
600                    (row, col, val, b, cones, q_object),
601                ) {
602                    return Err(CoppError::InvalidInput(
603                        "clarabel_objective_copp3".into(),
604                        "Invalid TotalVariationTorque objective".into(),
605                    ));
606                }
607            }
608            CoppObjective::Linear(weight, alpha, beta) => {
609                if !clarabel_objective_linear_copp3(
610                    &s,
611                    *weight,
612                    alpha,
613                    beta,
614                    q_object,
615                    num_stationary,
616                ) {
617                    return Err(CoppError::InvalidInput(
618                        "clarabel_objective_copp3".into(),
619                        "Invalid Linear objective".into(),
620                    ));
621                }
622            }
623        }
624    }
625    Ok(())
626}
627
628/// Add the constraints and objective for Time in COPP3 optimization.
629/// num_val <= 5*n, num_b <= 4*n, num_cones <= n, n_var <= n
630fn clarabel_objective_time_copp3(
631    s: &[f64],
632    weight: f64,
633    weight_a: &[f64],
634    num_stationary: (usize, usize),
635    objective_constraints: ObjConsClarabel,
636) -> bool {
637    if weight < 0.0 {
638        return false;
639    }
640
641    let (row, col, val, b, cones, q_object) = objective_constraints;
642    let n = s.len() - 1;
643    let len_xi = length_xi(n, num_stationary);
644    let k_skip = skip_a_for_xi(num_stationary.0);
645    // Add constraints for xi and eta, where xi[k]=sqrt(a[k+k_skip]), eta[k]=1/xi[k]
646    // eta[k] >= 0,
647    // norm2([2, xi[k] - eta[k]]) <= xi[k] + eta[k]
648    let id_xi_start = 2 * (n + 1);
649    let id_eta_start = q_object.len();
650    // Step 1. eta[k] >= 0
651    // A*x-b = -s = -1*eta[k] = -1*x[id_eta_start + k] <= 0
652    row.extend(b.len()..(b.len() + len_xi));
653    col.extend(id_eta_start..(id_eta_start + len_xi));
654    val.resize(val.len() + len_xi, -1.0);
655    b.resize(b.len() + len_xi, 0.0);
656    cones.push(NonnegativeConeT(len_xi));
657    // Step 2. norm2([2, xi[k] - eta[k]]) <= xi[k] + eta[k]
658    // -A*x+b = s = [xi[k] + eta[k], xi[k] - eta[k], 2] \in SOC
659    for k in 0..len_xi {
660        // -A*x+b = s = [x[id_xi_start + k] + x[id_eta_start + k], x[id_xi_start + k] - x[id_eta_start + k], 2] \in SOC
661        col.extend([
662            id_xi_start + k,
663            id_eta_start + k,
664            id_xi_start + k,
665            id_eta_start + k,
666        ]);
667        row.extend([b.len(), b.len(), b.len() + 1, b.len() + 1]);
668        val.extend([-1.0, -1.0, -1.0, 1.0]);
669        b.extend([0.0, 0.0, 2.0]);
670    }
671    cones.resize(cones.len() + len_xi, SecondOrderConeT(3));
672    // Minimize sum[k in 0..len_xi] {weight_a[k+k_skip] / sqrt(a[k+k_skip])}
673    // Minimize sum[k in 0..len_xi] {weight_a[k+k_skip] * eta[k]}
674    q_object.extend(
675        weight_a
676            .iter()
677            .skip(k_skip)
678            .take(len_xi)
679            .map(|w| weight * w),
680    );
681
682    true
683}
684
685/// Add the constraints and objective for ThermalEnergy in COPP3 optimization.
686/// num_val <= (4+2*dim)*(n+1), num_b <= (2+dim)*(n+1), num_cones <= n+1, n_var <= n+1
687fn clarabel_objective_thermal_energy_copp3(
688    weight_a: &[f64],
689    weight: f64,
690    normalize: &[f64],
691    coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
692    num_stationary: (usize, usize),
693    objective_constraints: ObjConsClarabel,
694) -> bool {
695    if weight < 0.0 {
696        return false;
697    }
698    let (row, col, val, b, cones, q_object) = objective_constraints;
699    // minimize: weight * \int_{s[0]}^{s[n]} {\sum[i] {(tau[i][s] * normalize[i])^2 / sqrt(a[s])} ds}
700    let mut coeff_a = coeffs_torque.0.clone();
701    let mut coeff_b = coeffs_torque.1.clone();
702    let mut coeff_g = coeffs_torque.2.clone();
703    let dim = coeff_a.nrows();
704    if normalize.len() != dim {
705        return false;
706    }
707    let n = coeff_a.ncols() - 1;
708    // tau[i][k] = coeff_a[i][k] * a[k] + coeff_b[i][k] * b[k] + coeff_g[i][k]
709    let normalize = DVectorView::from_slice(normalize, dim);
710    for mut col in coeff_a.column_iter_mut() {
711        col.component_mul_assign(&normalize);
712    }
713    for mut col in coeff_b.column_iter_mut() {
714        col.component_mul_assign(&normalize);
715    }
716    for mut col in coeff_g.column_iter_mut() {
717        col.component_mul_assign(&normalize);
718    }
719    // tau[i][k] * normalize[i] = coeff_a[i][k] * a[k] + coeff_b[i][k] * b[k] + coeff_g[i][k]
720    let k_skip = skip_a_for_xi(num_stationary.0);
721    let len_xi = length_xi(n, num_stationary);
722
723    if num_stationary.0 > 0 {
724        // minimize: weight * \int_{s[0]}^{s[num_stationary.0]} {\sum[i] {tau_normal[i][s]^2 / sqrt(a[s])} ds}
725        // \approx weight * \sum[i] { tau_average[i]^2 * \int_{s[0]}^{s[num_stationary.0]} {1 / sqrt(a[s])} ds} }
726        // \int_{s[0]}^{s[num_stationary.0]} {1 / sqrt(a[s])} ds} = weight[0] / xi[0]
727        // minmize: weight * weight_a[0] * \sum[i] { tau_average[i]^2 / xi[0] }
728        // let tau_average[i] = (tau_normal[i][0] + tau_normal[i][num_stationary.0]) / 2
729        let col_a = coeff_a.column(num_stationary.0);
730        let col_b = coeff_b.column(num_stationary.0);
731        let col_g = coeff_g.column(num_stationary.0);
732        let col_g_0 = coeff_g.column(0);
733        // tau[0] = col_g_0
734        // tau[num_stationary.0] = col_a * a[num_stationary.0] + col_b * b[num_stationary.0] + col_g
735        // let \sum[i] { tau_average[i]^2 } <= t * xi[0]
736        // \sum[i](col_a[i] * a[num_stationary.0] + col_b[i] * b[num_stationary.0] + col_g[i] + col_g_0[i])^2 <= 4 * t
737        // -A*x+b = s = [t + xi[0], t - xi[0], -(col_a[i] * a[num_stationary.0] + col_b[i] * b[num_stationary.0] + col_g[i] + col_g_0[i])] \in SOC
738        let id_t = q_object.len();
739        // -A*x+b = [t + xi[0], t - xi[0]]
740        row.extend(b.len()..(b.len() + 2));
741        col.resize(col.len() + 2, id_t);
742        row.extend(b.len()..(b.len() + 2));
743        col.resize(col.len() + 2, 2 * (n + 1));
744        val.resize(val.len() + 3, -1.0);
745        val.push(1.0);
746        b.resize(b.len() + 2, 0.0);
747        // -A*x+b = [-(col_a[i] * a[num_stationary.0] + col_b[i] * b[num_stationary.0] + col_g[i] + col_g_0[i])] for i in 0..dim
748        row.extend((b.len())..(b.len() + dim));
749        col.resize(col.len() + dim, num_stationary.0);
750        val.extend(col_a.iter().take(dim));
751        row.extend((b.len())..(b.len() + dim));
752        col.resize(col.len() + dim, n + 1 + num_stationary.0);
753        val.extend(col_b.iter().take(dim));
754        b.extend(
755            col_g
756                .iter()
757                .zip(col_g_0.iter())
758                .take(dim)
759                .map(|(&g, &g_0)| -(g + g_0)),
760        );
761        cones.push(SecondOrderConeT(dim + 2));
762        q_object.push(weight * weight_a[0]);
763    }
764    if num_stationary.1 > 0 {
765        // minimize: weight * \int_{s[n-num_stationary.1]}^{s[n]} {\sum[i] {tau_normal[i][s]^2 / sqrt(a[s])} ds}
766        // \approx weight * \sum[i] { tau_average[i]^2 * \int_{s[n-num_stationary.1]}^{s[n]} {1 / sqrt(a[s])} ds} }
767        // \int_{s[n-num_stationary.1]}^{s[n]} {1 / sqrt(a[s])} ds} = weight[n-num_stationary.1] / xi[len_xi-1]
768        // minmize: weight * weight[n] * \sum[i] { tau_average[i]^2 }
769        // let tau_average[i] = (tau_normal[i][n] + tau_normal[i][n-num_stationary.1]) / 2
770        let col_a = coeff_a.column(n - num_stationary.1);
771        let col_b = coeff_b.column(n - num_stationary.1);
772        let col_g = coeff_g.column(n - num_stationary.1);
773        let col_g_f = coeff_g.column(n);
774        // tau[f] = col_g_n
775        // tau[n-num_stationary.1] = col_a * a[n-num_stationary.1] + col_b * b[n-num_stationary.1] + col_g
776        // let \sum[i] { tau_average[i]^2 } <= t * xi[len_xi-1]
777        // \sum[i](col_a[i] * a[n-num_stationary.1] + col_b[i] * b[n-num_stationary.1] + col_g[i] + col_g_f[i])^2 <= 4 * t * xi[len_xi-1]
778        // -A*x+b = s = [t + xi[len_xi-1], t - xi[len_xi-1], -(col_a[i] * a[n-num_stationary.1] + col_b[i] * b[n-num_stationary.1] + col_g[i] + col_g_f[i])] \in SOC
779        let id_t = q_object.len();
780        // -A*x+b = [t+xi[len_xi-1], t-xi[len_xi-1]]
781        row.extend(b.len()..(b.len() + 2));
782        col.resize(col.len() + 2, id_t);
783        row.extend(b.len()..(b.len() + 2));
784        col.resize(col.len() + 2, 2 * n + len_xi + 1);
785        val.resize(val.len() + 3, -1.0);
786        val.push(1.0);
787        b.resize(b.len() + 2, 0.0);
788        // -A*x+b = [-(col_a[i] * a[n-num_stationary.1] + col_b[i] * b[n-num_stationary.1] + col_g[i] + col_g_f[i])] for i in 0..dim
789        row.extend((b.len())..(b.len() + dim));
790        col.resize(col.len() + dim, n - num_stationary.1);
791        val.extend(col_a.iter().take(dim));
792        row.extend((b.len())..(b.len() + dim));
793        col.resize(col.len() + dim, 2 * n + 1 - num_stationary.1);
794        val.extend(col_b.iter().take(dim));
795        b.extend(
796            col_g
797                .iter()
798                .zip(col_g_f.iter())
799                .take(dim)
800                .map(|(&g, &g_f)| -(g + g_f)),
801        );
802        cones.push(SecondOrderConeT(dim + 2));
803        q_object.push(weight * weight_a[n]);
804    }
805
806    // minimize: weight * \sum[k] { \int_{s[k]}^{s[k+1]} {\sum[i] {tau_normal[i][s]^2 / sqrt(a[s])} ds} }
807    // Decouple the integral
808    // minimize: weight * \sum[k] { \sum[i] {tau_normal[i][k]^2} / sqrt(a[k]) * 0.5 * (s[k+1]-s[k-1]) }
809    let id_t_start = q_object.len();
810    for (k, (col_a, col_b, col_g)) in izip!(
811        coeff_a.column_iter(),
812        coeff_b.column_iter(),
813        coeff_g.column_iter()
814    )
815    .skip(k_skip)
816    .take(len_xi)
817    .enumerate()
818    {
819        // \sum[i] {(col_a[i] * a[k+k_skip] + col_b[i] * b[k+k_skip] + col_g[i])^2} / xi[k] <= 4 * t[k]
820        // \sum[i] {(col_a[i] * a[k+k_skip] + col_b[i] * b[k+k_skip] + col_g[i])^2} <= 4 * t[k] * xi[k]
821        // -A*x+b = s = [t[k] + xi[k], t[k] - xi[k], -(col_a[i] * a[k+k_skip] + col_b[i] * b[k+k_skip] + col_g[i])] \in SOC
822        // -A*x+b = s = [t[k] + xi[k], t[k] - xi[k]]
823        row.extend(b.len()..(b.len() + 2));
824        col.resize(col.len() + 2, id_t_start + k);
825        row.extend(b.len()..(b.len() + 2));
826        col.resize(col.len() + 2, 2 * (n + 1) + k);
827        val.resize(val.len() + 3, -1.0);
828        val.push(1.0);
829        b.resize(b.len() + 2, 0.0);
830        // -A*x+b = s = [-(col_a[i] * a[k+k_skip] + col_b[i] * b[k+k_skip] + col_g[i])]
831        row.extend((b.len())..(b.len() + dim));
832        col.resize(col.len() + dim, k + k_skip);
833        val.extend(col_a.iter().take(dim));
834        row.extend((b.len())..(b.len() + dim));
835        col.resize(col.len() + dim, n + 1 + k + k_skip);
836        val.extend(col_b.iter().take(dim));
837        b.extend(col_g.iter().take(dim).map(|&g| -g));
838    }
839    cones.resize(cones.len() + len_xi, SecondOrderConeT(dim + 2));
840    // minimize: 2 * weight * \sum[k] { t[k] * (s[k+1]-s[k-1]) }
841    // if num_stationary == (0,0), then \sum[k in 1..n] { t[k] * (s[k+1]-s[k-1]) }
842    // if num_stationary == (n1>0,n2>0), then \sum[k in (n1+1)..(n-n2-1)] { t[k] * (s[k+1]-s[k-1]) } + t[n1] * (s[n1+1]-s[n1]) + t[n-n2] * (s[n-n2]-s[n-n2-1])
843    let weight_four = 4.0 * weight;
844    q_object.extend(
845        weight_a
846            .iter()
847            .skip(k_skip)
848            .take(len_xi)
849            .map(|w| weight_four * w),
850    );
851    true
852}
853
854/// Add the constraints and objective for TotalVariationTorque in COPP3 optimization.
855/// num_val <= 10*n*dim, num_b <= 2*n*dim, num_cones <= 1, n_var <= n*dim
856fn clarabel_objective_tv_torque_copp3(
857    weight: f64,
858    normalize: &[f64],
859    coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
860    num_stationary: (usize, usize),
861    objective_constraints: ObjConsClarabel,
862) -> bool {
863    if weight < 0.0 {
864        return false;
865    }
866    let (row, col, val, b, cones, q_object) = objective_constraints;
867    // minimize: weight * \sum |tau[i][k+1]-tau[i][k]| * normalize[i]
868    // Let: |tau[i][k+1]-tau[i][k]| * normalize[i] <= t[i][k]
869    let mut coeff_a = coeffs_torque.0.clone();
870    let mut coeff_b = coeffs_torque.1.clone();
871    let mut coeff_g = coeffs_torque.2.clone();
872    let dim = coeff_a.nrows();
873    if normalize.len() != dim {
874        return false;
875    }
876    let n = coeff_a.ncols() - 1;
877    // tau[i][k] = coeff_a[i][k] * a[k] + coeff_b[i][k] * b[k] + coeff_g[i][k]
878    let normalize = DVectorView::from_slice(normalize, dim);
879    for mut col in coeff_a.column_iter_mut() {
880        col.component_mul_assign(&normalize);
881    }
882    for mut col in coeff_b.column_iter_mut() {
883        col.component_mul_assign(&normalize);
884    }
885    for mut col in coeff_g.column_iter_mut() {
886        col.component_mul_assign(&normalize);
887    }
888    // tau[i][k] * normalize[i] = coeff_a[i][k] * a[k] + coeff_b[i][k] * b[k] + coeff_g[i][k]
889    // (tau[i][k+1] - tau[i][k]) * normalize[i] = coeff_a[i][k+1] * a[k+1] + coeff_b[i][k+1] * b[k+1] + coeff_g[i][k+1] - coeff_a[i][k] * a[k] - coeff_b[i][k] * b[k] - coeff_g[i][k]
890
891    let n_b_old = b.len();
892    // A*x-b = -s = -coeff_a[i][k] * x[k] + coeff_a[i][k+1] * x[k+1] - coeff_b[i][k] * x[n+k+1] + coeff_b[i][k+1] * x[n+k+2] + coeff_g[i][k+1] - coeff_g[i][k] - t[i][k] <= 0
893    // A*x-b = -s = -(-coeff_a[i][k] * x[k] + coeff_a[i][k+1] * x[k+1] - coeff_b[i][k] * x[n+k+1] + coeff_b[i][k+1] * x[n+k+2] + coeff_g[i][k+1] - coeff_g[i][k] - t[i][k]) <= 0
894    if num_stationary.0 > 0 {
895        // Consider (tau[i][num_stationary.0] - tau[i][0]) * normalize[i] = coeff_a[i][num_stationary.0] * a[num_stationary.0] + coeff_b[i][num_stationary.0] * b[num_stationary.0] + coeff_g[i][num_stationary.0] - coeff_g[i][0]
896        let col_a = coeff_a.column(num_stationary.0);
897        let col_b = coeff_b.column(num_stationary.0);
898        let col_g = coeff_g.column(num_stationary.0);
899        let col_g_0 = coeff_g.column(0);
900        let n_var_old = q_object.len();
901        for (i, (&v_a, &v_b, &v_g, &v_g_0)) in
902            izip!(col_a.iter(), col_b.iter(), col_g.iter(), col_g_0.iter())
903                .take(dim)
904                .enumerate()
905        {
906            // dtau_normal = v_a * a[num_stationary.0] + v_b * b[num_stationary.0] + v_g - v_g_0
907            // A*x-b = -s = v_a * a[num_stationary.0] + v_b * b[num_stationary.0] + v_g - v_g_0 - t[i] <= 0
908            row.resize(row.len() + 3, b.len());
909            col.extend([num_stationary.0, n + num_stationary.0 + 1, n_var_old + i]);
910            val.extend([v_a, v_b, -1.0]);
911            b.push(v_g - v_g_0);
912            // A*x-b = -s = -(v_a * a[num_stationary.0] + v_b * b[num_stationary.0] + v_g - v_g_0) - t[i] <= 0
913            row.resize(row.len() + 3, b.len());
914            col.extend([num_stationary.0, n + num_stationary.0 + 1, n_var_old + i]);
915            val.extend([-v_a, -v_b, -1.0]);
916            b.push(v_g_0 - v_g);
917        }
918        q_object.resize(q_object.len() + dim, weight);
919    }
920
921    if num_stationary.1 > 0 {
922        // Consider (tau[i][n-num_stationary.1] - tau[i][n]) * normalize[i] = coeff_a[i][n-num_stationary.1] * a[n-num_stationary.1] + coeff_b[i][n-num_stationary.1] * b[n-num_stationary.1] + coeff_g[i][n-num_stationary.1] - coeff_g[i][n]
923        let col_a = coeff_a.column(n - num_stationary.1);
924        let col_b = coeff_b.column(n - num_stationary.1);
925        let col_g = coeff_g.column(n - num_stationary.1);
926        let col_g_f = coeff_g.column(n);
927        let n_var_old = q_object.len();
928        for (i, (&v_a, &v_b, &v_g, &v_g_f)) in
929            izip!(col_a.iter(), col_b.iter(), col_g.iter(), col_g_f.iter())
930                .take(dim)
931                .enumerate()
932        {
933            // dtau_normal = v_a * a[n-num_stationary.1] + v_b * b[n-num_stationary.1] + v_g - v_g_0
934            // A*x-b = -s = v_a * a[n-num_stationary.1] + v_b * b[n-num_stationary.1] + v_g - v_g_0 - t[i] <= 0
935            row.resize(row.len() + 3, b.len());
936            col.extend([
937                n - num_stationary.1,
938                n + n - num_stationary.1 + 1,
939                n_var_old + i,
940            ]);
941            val.extend([v_a, v_b, -1.0]);
942            b.push(v_g - v_g_f);
943            // A*x-b = -s = -(v_a * a[n-num_stationary.1] + v_b * b[n-num_stationary.1] + v_g - v_g_f) - t[i] <= 0
944            row.resize(row.len() + 3, b.len());
945            col.extend([
946                n - num_stationary.1,
947                n + n - num_stationary.1 + 1,
948                n_var_old + i,
949            ]);
950            val.extend([-v_a, -v_b, -1.0]);
951            b.push(v_g_f - v_g);
952        }
953        q_object.resize(q_object.len() + dim, weight);
954    }
955
956    // Consider (tau[i][k+1] - tau[i][k]) * normalize[i] for k in num_stationary.0..(n-num_stationary.1)
957    for (k, ((col_a_curr, col_b_curr, col_g_curr), (col_a_next, col_b_next, col_g_next))) in izip!(
958        coeff_a.column_iter(),
959        coeff_b.column_iter(),
960        coeff_g.column_iter()
961    )
962    .tuple_windows()
963    .enumerate()
964    .skip(num_stationary.0)
965    .take(n - num_stationary.0 - num_stationary.1)
966    {
967        let n_var_old = q_object.len();
968        for (i, (&v_a_curr, &v_b_curr, &v_g_curr, &v_a_next, &v_b_next, &v_g_next)) in izip!(
969            col_a_curr.iter(),
970            col_b_curr.iter(),
971            col_g_curr.iter(),
972            col_a_next.iter(),
973            col_b_next.iter(),
974            col_g_next.iter()
975        )
976        .enumerate()
977        {
978            // dtau_normal[i] = -v_a_curr * a[k] + v_a_next * a[k+1] - v_b_curr * b[k] + v_b_next * b[k+1] + v_g_next- v_g_curr
979            // A*x-b = -s = -v_a_curr * a[k] + v_a_next * a[k+1] - v_b_curr * b[k] + v_b_next * b[k+1] + v_g_next - v_g_curr - t[i][k] <= 0
980            row.resize(row.len() + 5, b.len());
981            col.extend([k, k + 1, n + k + 1, n + k + 2, n_var_old + i]);
982            val.extend([-v_a_curr, v_a_next, -v_b_curr, v_b_next, -1.0]);
983            b.push(v_g_next - v_g_curr);
984            // A*x-b = -s = -(-v_a_curr * a[k] + v_a_next * a[k+1] - v_b_curr * b[k] + v_b_next * b[k+1] + v_g_next - v_g_curr) - t[i][k] <= 0
985            row.resize(row.len() + 5, b.len());
986            col.extend([k, k + 1, n + k + 1, n + k + 2, n_var_old + i]);
987            val.extend([v_a_curr, -v_a_next, v_b_curr, -v_b_next, -1.0]);
988            b.push(v_g_curr - v_g_next);
989        }
990        q_object.resize(q_object.len() + dim, weight);
991    }
992    cones.push(NonnegativeConeT(b.len() - n_b_old));
993
994    true
995}
996
997/// Add the constraints and objective for Linear in COPP3 optimization.
998fn clarabel_objective_linear_copp3(
999    s: &[f64],
1000    weight: f64,
1001    alpha: &[f64],
1002    beta: &[f64],
1003    q_object: &mut [f64],
1004    num_stationary: (usize, usize),
1005) -> bool {
1006    if alpha.len() != s.len() || beta.len() != s.len() {
1007        return false;
1008    }
1009    let n = s.len() - 1;
1010    // objective: minimize weight * \sum (alpha[k]*a[k] + beta[k]*b[k])
1011    if num_stationary.0 > 1 {
1012        let &s_start = s.first().unwrap();
1013        let ds_start = s[num_stationary.0] - s_start;
1014        let q_n1 = &mut q_object[num_stationary.0];
1015        for (&s_k, &alpha_k, &beta_k) in izip!(s.iter(), alpha.iter(), beta.iter())
1016            .skip(1)
1017            .take(num_stationary.0 - 1)
1018        {
1019            let dsk_start = s_k - s_start;
1020            let gamma = dsk_start / ds_start;
1021            // a_k = a[num_stationary.0] * gamma;
1022            // b_k = a[num_stationary.0] * gamma / (1.5 * dsk_start);
1023            *q_n1 += weight * gamma * gamma.cbrt() * (alpha_k + beta_k / (1.5 * dsk_start));
1024        }
1025    }
1026    if num_stationary.1 > 1 {
1027        let &s_final = s.last().unwrap();
1028        let ds_final = s[n - num_stationary.1] - s_final;
1029        let q_n2 = &mut q_object[n - num_stationary.1];
1030        for (&s_k, &alpha_k, &beta_k) in izip!(s.iter(), alpha.iter(), beta.iter())
1031            .skip(1)
1032            .take(num_stationary.1 - 1)
1033        {
1034            let dsk_final = s_k - s_final;
1035            let gamma = dsk_final / ds_final;
1036            // a_k = a[n - num_stationary.1] * gamma;
1037            // b_k = a[n - num_stationary.1] * gamma / (1.5 * dsk_start);
1038            *q_n2 += weight * gamma * gamma.cbrt() * (alpha_k + beta_k / (1.5 * dsk_final));
1039        }
1040    }
1041    for (q_k, &alpha_k) in q_object
1042        .iter_mut()
1043        .zip(alpha.iter())
1044        .take(n + 1 - num_stationary.1)
1045        .skip(num_stationary.0)
1046    {
1047        *q_k += weight * alpha_k;
1048    }
1049    for (q_k, &beta_k) in q_object
1050        .iter_mut()
1051        .skip(n + 1)
1052        .zip(beta.iter())
1053        .take(n + 1 - num_stationary.1)
1054        .skip(num_stationary.0)
1055    {
1056        *q_k += weight * beta_k;
1057    }
1058
1059    true
1060}
1061
1062/// Compute the time value in COPP3 optimization.
1063/// Input: a_sqrt_down = 1 / sqrt(a)
1064#[cfg(any(feature = "c", feature = "python", test))]
1065#[inline(always)]
1066fn objective_value_time_copp3(
1067    a_sqrt_down: &[f64],
1068    weight_a: &[f64],
1069    num_stationary: (usize, usize),
1070) -> f64 {
1071    let n = a_sqrt_down.len() - 1;
1072    let k_skip = skip_a_for_xi(num_stationary.0);
1073    let len_xi = length_xi(n, num_stationary);
1074    // objective: minimize \sum  weight_a[k] / sqrt(a[k])
1075    let mut objective = 0.0;
1076    for (a_sqrt_down, weight_a) in a_sqrt_down
1077        .iter()
1078        .zip(weight_a.iter())
1079        .skip(k_skip)
1080        .take(len_xi)
1081    {
1082        objective += weight_a * a_sqrt_down;
1083    }
1084    objective
1085}
1086
1087/// Compute the thermal energy value in COPP3 optimization.
1088#[cfg(any(feature = "c", feature = "python", test))]
1089#[inline(always)]
1090fn objective_value_thermal_energy_copp3(
1091    a_sqrt_down: &[f64],
1092    weight_a: &[f64],
1093    num_stationary: (usize, usize),
1094    torque: &DMatrix<f64>,
1095    normalize: &[f64],
1096) -> f64 {
1097    let mut objective = 0.0;
1098    let n = a_sqrt_down.len() - 1;
1099    if num_stationary.0 > 0 {
1100        // minmize: weight_a[0] / sqrt(a[num_stationary.0]) * \sum[i] { (tau_average[i] * normalize[i])^2 }
1101        // tau_average[i] = (tau_normal[i][0] + tau_normal[i][num_stationary.0]) / 2
1102        let torque_n1 = torque.column(num_stationary.0);
1103        let torque_0 = torque.column(0);
1104        let weight = weight_a[0] * a_sqrt_down[num_stationary.0];
1105        for (tau_n1, tau_0, &normalize_i) in
1106            izip!(torque_n1.iter(), torque_0.iter(), normalize.iter())
1107        {
1108            let tau_average = 0.5 * normalize_i * (tau_n1 + tau_0);
1109            objective += weight * tau_average * tau_average;
1110        }
1111    }
1112    if num_stationary.1 > 0 {
1113        // minmize: weight_a[n] / sqrt(a[n-num_stationary.1]) * \sum[i] { (tau_average[i] * normalize[i])^2 }
1114        // tau_average[i] = (tau_normal[i][n] + tau_normal[i][n-num_stationary.1]) / 2
1115        let torque_n2 = torque.column(n - num_stationary.1);
1116        let torque_n = torque.column(n);
1117        let weight = weight_a[n] * a_sqrt_down[n - num_stationary.1];
1118        for (tau_n2, tau_n, &normalize_i) in
1119            izip!(torque_n2.iter(), torque_n.iter(), normalize.iter())
1120        {
1121            let tau_average = 0.5 * normalize_i * (tau_n2 + tau_n);
1122            objective += weight * tau_average * tau_average;
1123        }
1124    }
1125    let k_skip = skip_a_for_xi(num_stationary.0);
1126    let len_xi = length_xi(n, num_stationary);
1127    for (torque_k, &a_sqrt_down_k, &weight_a_k) in
1128        izip!(torque.column_iter(), a_sqrt_down.iter(), weight_a.iter())
1129            .skip(k_skip)
1130            .take(len_xi)
1131    {
1132        for (tau_k, &normalize_i) in torque_k.iter().zip(normalize.iter()) {
1133            let tau_normal = tau_k * normalize_i;
1134            objective += 4.0 * weight_a_k * tau_normal * tau_normal * a_sqrt_down_k;
1135        }
1136    }
1137
1138    objective
1139}
1140
1141/// Compute the thermal energy value in COPP3 optimization.
1142#[cfg(any(feature = "c", feature = "python", test))]
1143#[inline(always)]
1144fn objective_value_tv_torque_copp3(
1145    torque: &DMatrix<f64>,
1146    normalize: &[f64],
1147    num_stationary: (usize, usize),
1148) -> f64 {
1149    let mut objective = 0.0;
1150    let n = torque.ncols() - 1;
1151    if num_stationary.0 > 0 {
1152        // |tau[i][num_stationary.0] - tau[i][0]| * normalize[i]
1153        let torque_n1 = torque.column(num_stationary.0);
1154        let torque_0 = torque.column(0);
1155        for (tau_n1, tau_0, &normalize_i) in
1156            izip!(torque_n1.iter(), torque_0.iter(), normalize.iter())
1157        {
1158            objective += normalize_i * (tau_n1 - tau_0).abs();
1159        }
1160    }
1161    if num_stationary.1 > 0 {
1162        // |tau[i][n-num_stationary.1] - tau[i][n]| * normalize[i]
1163        let torque_n2 = torque.column(n - num_stationary.1);
1164        let torque_n = torque.column(n);
1165        for (tau_n2, tau_n, &normalize_i) in
1166            izip!(torque_n2.iter(), torque_n.iter(), normalize.iter())
1167        {
1168            objective += normalize_i * (tau_n2 - tau_n).abs();
1169        }
1170    }
1171    // Consider (tau[i][k+1] - tau[i][k]) * normalize[i] for k in num_stationary.0..(n-num_stationary.1)
1172    for (torque_col_curr, torque_col_next) in torque
1173        .column_iter()
1174        .tuple_windows()
1175        .skip(num_stationary.0)
1176        .take(n - num_stationary.0 - num_stationary.1)
1177    {
1178        for (tau_k, tau_k_next, &normalize_i) in izip!(
1179            torque_col_curr.iter(),
1180            torque_col_next.iter(),
1181            normalize.iter()
1182        ) {
1183            objective += normalize_i * (tau_k_next - tau_k).abs();
1184        }
1185    }
1186
1187    objective
1188}
1189
1190/// Compute the objective value for Linear in COPP3 optimization.
1191#[cfg(any(feature = "c", feature = "python", test))]
1192#[inline(always)]
1193fn objective_value_linear_copp3(
1194    a_profile: &[f64],
1195    b_profile: &[f64],
1196    alpha: &[f64],
1197    beta: &[f64],
1198) -> f64 {
1199    // objective: minimize \sum (alpha[k]*a[k] + beta[k]*b[k])
1200    let mut objective = 0.0;
1201    for (a_curr, alpha_curr) in a_profile.iter().zip(alpha.iter()) {
1202        // alpha[k]*a[k]
1203        objective += a_curr * alpha_curr;
1204    }
1205    for (b_curr, beta_curr) in b_profile.iter().zip(beta.iter()) {
1206        // beta[k]*b[k]
1207        objective += b_curr * beta_curr;
1208    }
1209    objective
1210}
1211
1212/// Compute the objective value for COPP3 optimization.
1213#[cfg(any(feature = "c", feature = "python", test))]
1214pub(crate) fn objective_value_copp3_opt<M: RobotTorque>(
1215    problem: &Copp3Problem<M>,
1216    profile: Topp3ProfileRef<'_>,
1217) -> (f64, Vec<f64>) {
1218    let (a_profile, b_profile, num_stationary) = profile;
1219    let s = problem
1220        .robot
1221        .constraints
1222        .s_vec(problem.idx_s_start, problem.idx_s_start + a_profile.len());
1223    let Ok(s) = s else {
1224        return (f64::INFINITY, vec![0.0; problem.objectives.len()]);
1225    };
1226    if a_profile.len() != s.len() || b_profile.len() != s.len() {
1227        return (f64::INFINITY, vec![0.0; problem.objectives.len()]);
1228    }
1229    let (a_sqrt_down, weight_a_time) = if problem.objectives.iter().any(|obj| {
1230        matches!(
1231            obj,
1232            CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
1233        )
1234    }) {
1235        (
1236            a_profile
1237                .iter()
1238                .map(|a| 1.0 / a.sqrt().max(1E-16))
1239                .collect(),
1240            get_weight_a_topp3(&s, num_stationary),
1241        )
1242    } else {
1243        (vec![], vec![])
1244    };
1245    let weight_a_torque = if problem
1246        .objectives
1247        .iter()
1248        .any(|obj| matches!(obj, CoppObjective::ThermalEnergy(_, _)))
1249    {
1250        get_weight_a_copp3(&s, num_stationary)
1251    } else {
1252        vec![]
1253    };
1254    let torque = if problem.objectives.iter().any(|obj| {
1255        matches!(
1256            obj,
1257            CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
1258        )
1259    }) {
1260        let torque_result =
1261            problem
1262                .robot
1263                .get_torque_with_ab(a_profile, b_profile, problem.idx_s_start);
1264        match torque_result {
1265            Ok(torque) => torque,
1266            _ => return (f64::INFINITY, vec![0.0; problem.objectives.len()]),
1267        }
1268    } else {
1269        DMatrix::<f64>::zeros(0, 0)
1270    };
1271    let mut obj_val = Vec::with_capacity(problem.objectives.len());
1272    let mut obj_val_total = 0.0;
1273    for obj in problem.objectives {
1274        match obj {
1275            CoppObjective::Time(weight) => {
1276                let obj_here =
1277                    objective_value_time_copp3(&a_sqrt_down, &weight_a_time, num_stationary);
1278                obj_val.push(obj_here);
1279                obj_val_total += weight * obj_here;
1280            }
1281            CoppObjective::ThermalEnergy(weight, normalize) => {
1282                let obj_here = objective_value_thermal_energy_copp3(
1283                    &a_sqrt_down,
1284                    &weight_a_torque,
1285                    num_stationary,
1286                    &torque,
1287                    normalize,
1288                );
1289                obj_val.push(obj_here);
1290                obj_val_total += weight * obj_here;
1291            }
1292            CoppObjective::TotalVariationTorque(weight, normalize) => {
1293                let obj_here = objective_value_tv_torque_copp3(&torque, normalize, num_stationary);
1294                obj_val.push(obj_here);
1295                obj_val_total += weight * obj_here;
1296            }
1297            CoppObjective::Linear(weight, alpha, beta) => {
1298                let obj_here = objective_value_linear_copp3(a_profile, b_profile, alpha, beta);
1299                obj_val.push(obj_here);
1300                obj_val_total += weight * obj_here;
1301            }
1302        }
1303    }
1304    (obj_val_total, obj_val)
1305}
1306
1307#[cfg(test)]
1308mod tests {
1309    use super::*;
1310    use crate::copp::copp2::stable::basic::{Topp2ProblemBuilder, s_to_t_topp2};
1311    use crate::copp::copp2::stable::reach_set2::ReachSet2OptionsBuilder;
1312    use crate::copp::copp2::stable::topp2_ra::topp2_ra;
1313    use crate::copp::copp3::stable::basic::{Copp3ProblemBuilder, s_to_t_topp3};
1314    use crate::copp::copp3::stable::topp3_lp::topp3_lp;
1315    use crate::copp::copp3::stable::topp3_socp::topp3_socp;
1316    use crate::copp::{ClarabelOptionsBuilder, default_clarabel_settings};
1317    use crate::path::{add_symmetric_axial_limits_for_test, lissajous_path_for_test};
1318    use crate::robot::robot_core::Robot;
1319    use std::time::Instant;
1320    use std::vec;
1321
1322    #[test]
1323    fn test_copp3_lp() -> Result<(), CoppError> {
1324        run_test_copp3_lp_repeated(1, false)
1325    }
1326
1327    /// Conditions: release, --include-ignored, CPU = Intel(R) Core(TM) Ultra 9 285K.
1328    /// Average 100 experiments: tc_ra = 0.3270 ms, tc_lp = 237.4646 ms, tc_copp = 232.0414 ms, tf_ra = 6.304760, tf_lp = 6.524883, tf_copp = 6.524883, obj_lp = -0.031621, obj_copp = -0.031621
1329    #[test]
1330    #[ignore = "slow"]
1331    fn test_copp3_lp_robust() -> Result<(), CoppError> {
1332        run_test_copp3_lp_repeated(100, true)
1333    }
1334
1335    #[test]
1336    fn test_copp3_qp() -> Result<(), CoppError> {
1337        run_test_copp3_qp_repeated(1, false)
1338    }
1339
1340    /// Conditions: release, --include-ignored, CPU = Intel(R) Core(TM) Ultra 9 285K.
1341    /// Average 100 experiments (fail 0): tc_ra = 0.3452 ms, tc_qp = 329.8604 ms, tc_copp = 302.8725 ms, tf_ra = 6.122942, tf_qp = 6.342830, tf_copp = 6.342830, obj_qp = 6.348896, obj_copp = 6.348896
1342    #[test]
1343    #[ignore = "slow"]
1344    fn test_copp3_qp_robust() -> Result<(), CoppError> {
1345        run_test_copp3_qp_repeated(100, true)
1346    }
1347
1348    #[test]
1349    fn test_all_objectives() -> Result<(), CoppError> {
1350        run_test_all_objectives_repeated(1, false)
1351    }
1352
1353    /// Conditions: release, --include-ignored, CPU = Intel(R) Core(TM) Ultra 9 285K.
1354    /// Average 100 experiments (fail 0):
1355    /// Case 0: tc=301.449ms, obj=[6.355272621082504, 37.88484670012183, 30.538889633761094, 0.0014588588234334063]
1356    /// Case 1: tc=345.585ms, obj=[8.929951413267652, 11.232320784989641, 15.611002914308818, 0.001319403151583034]
1357    /// Case 2: tc=303.617ms, obj=[15.7679747735929, 2.0044043744079847, 5.695644764673545, 0.0010702913648672737]
1358    /// Case 3: tc=440.100ms, obj=[11.999657677994689, 5.6638102612967565, 5.999807538045939, 0.00022405699762245119]
1359    /// Case 4: tc=306.763ms, obj=[6.355272615415654, 37.884856482672014, 30.621301350094082, 0.0014588597553749254]
1360    #[test]
1361    #[ignore = "slow"]
1362    fn test_all_objectives_robust() -> Result<(), CoppError> {
1363        run_test_all_objectives_repeated(100, true)
1364    }
1365
1366    fn run_test_copp3_lp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
1367        let mut tc_sum_ra = 0.0;
1368        let mut tc_sum_lp = 0.0;
1369        let mut tc_sum_copp = 0.0;
1370        let mut tf_sum_ra = 0.0;
1371        let mut tf_sum_lp = 0.0;
1372        let mut tf_sum_copp = 0.0;
1373        let mut obj_sum_lp = 0.0;
1374        let mut obj_sum_copp = 0.0;
1375
1376        for i_exp in 0..n_exp {
1377            let n: usize = 1000;
1378            let dim = 7;
1379            let mut robot = Robot::with_capacity(dim, n);
1380
1381            let mut rng = rand::rng();
1382            let (s, path, omega, phi) =
1383                lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1384            robot
1385                .with_s(&s.as_view())?
1386                .with_q_from_path_3rd(&path, 0, n)?;
1387            add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0))?;
1388
1389            let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1390            // Step 1. Topp2-RA
1391            let start = Instant::now();
1392            let options_ra0 = ReachSet2OptionsBuilder::new()
1393                .lp_feas_tol(1E-9)
1394                .a_cmp_abs_tol(1E-9)
1395                .a_cmp_rel_tol(1E-9)
1396                .build()?;
1397            let a_ra0 = topp2_ra(&topp2_problem, &options_ra0)?;
1398            let tc_ra0 = start.elapsed().as_secs_f64() * 1E3;
1399            let (tf_ra0, _) = s_to_t_topp2(s.as_slice(), &a_ra0, 0.0)?;
1400
1401            let objectives = [CoppObjective::Linear(
1402                1.0,
1403                &get_weight_a_topp3(&s.as_slice()[0..n], (1, 1))
1404                    .iter()
1405                    .map(|&w_a| -w_a)
1406                    .collect_vec(),
1407                &vec![0.0; n],
1408            )];
1409            let copp3_problem = Copp3ProblemBuilder::new(
1410                &mut robot,
1411                &objectives,
1412                0,
1413                &a_ra0,
1414                (0.0, 0.0),
1415                (0.0, 0.0),
1416            )
1417            .build_with_linearization()?;
1418
1419            // Step 2. Test Copp3-SOCP
1420            let start = Instant::now();
1421            let profile_copp = {
1422                let mut settings = default_clarabel_settings();
1423                settings.tol_gap_rel = 1E-6;
1424                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1425                    .allow_almost_solved(true)
1426                    .build()?;
1427                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1428                if let Some(result) = result {
1429                    result
1430                } else {
1431                    return Err(CoppError::ClarabelSolverStatus(
1432                        "copp3_socp".into(),
1433                        solution.status,
1434                    ));
1435                }
1436            };
1437            let tc_copp = start.elapsed().as_secs_f64() * 1E3;
1438            // Test time profile generation
1439            let (tf_copp, _) = s_to_t_topp3(s.as_slice(), profile_copp.as_parts(), 0.0)?;
1440
1441            // Step 3. Test Topp3-LP
1442            let options_lp = ClarabelOptionsBuilder::new()
1443                .allow_almost_solved(true)
1444                .build()?;
1445            let start = Instant::now();
1446            let profile_lp = topp3_lp(&copp3_problem.as_topp3_problem(), &options_lp)?;
1447            let tc_lp = start.elapsed().as_secs_f64() * 1E3;
1448            // Test time profile generation
1449            let (tf_lp, _) = s_to_t_topp3(s.as_slice(), profile_lp.as_parts(), 0.0)?;
1450
1451            let (obj_lp, _) = objective_value_copp3_opt(&copp3_problem, profile_lp.as_parts());
1452            let (obj_copp, _) = objective_value_copp3_opt(&copp3_problem, profile_copp.as_parts());
1453
1454            if flag_print_step {
1455                crate::verbosity_log!(
1456                    crate::diag::Verbosity::Summary,
1457                    "Exp #{}: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_copp = {:.6}, obj_lp = {:.6}, obj_copp = {:.6}",
1458                    i_exp + 1,
1459                    tc_ra0,
1460                    tc_lp,
1461                    tc_copp,
1462                    tf_ra0,
1463                    tf_lp,
1464                    tf_copp,
1465                    obj_lp,
1466                    obj_copp
1467                );
1468            }
1469
1470            if (tf_copp - tf_lp).abs() > 1e-8 {
1471                crate::verbosity_log!(
1472                    crate::diag::Verbosity::Summary,
1473                    "omega = {omega:?}\nphi = {phi:?}"
1474                );
1475                crate::verbosity_log!(
1476                    crate::diag::Verbosity::Debug,
1477                    "COPP3 time optimality failed! tf_copp - tf_lp = {}",
1478                    tf_copp - tf_lp
1479                );
1480            }
1481
1482            tc_sum_ra += tc_ra0;
1483            tc_sum_lp += tc_lp;
1484            tc_sum_copp += tc_copp;
1485            tf_sum_ra += tf_ra0;
1486            tf_sum_lp += tf_lp;
1487            tf_sum_copp += tf_copp;
1488            obj_sum_lp += obj_lp;
1489            obj_sum_copp += obj_copp;
1490        }
1491
1492        crate::verbosity_log!(
1493            crate::diag::Verbosity::Summary,
1494            "Average {} experiments: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_copp = {:.6}, obj_lp = {:.6}, obj_copp = {:.6}",
1495            n_exp,
1496            tc_sum_ra / n_exp as f64,
1497            tc_sum_lp / n_exp as f64,
1498            tc_sum_copp / n_exp as f64,
1499            tf_sum_ra / n_exp as f64,
1500            tf_sum_lp / n_exp as f64,
1501            tf_sum_copp / n_exp as f64,
1502            obj_sum_lp / n_exp as f64,
1503            obj_sum_copp / n_exp as f64
1504        );
1505
1506        Ok(())
1507    }
1508
1509    fn run_test_copp3_qp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
1510        let mut tc_sum_ra = 0.0;
1511        let mut tc_sum_qp = 0.0;
1512        let mut tc_sum_copp = 0.0;
1513        let mut tf_sum_ra = 0.0;
1514        let mut tf_sum_qp = 0.0;
1515        let mut tf_sum_copp = 0.0;
1516        let mut obj_sum_qp = 0.0;
1517        let mut obj_sum_copp = 0.0;
1518        let mut succeed = 0;
1519
1520        for i_exp in 0..n_exp {
1521            let n: usize = 1000;
1522            let dim = 7;
1523            let mut robot = Robot::with_capacity(dim, n);
1524
1525            let mut rng = rand::rng();
1526            let (s, path, omega, phi) =
1527                lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1528            robot
1529                .with_s(&s.as_view())?
1530                .with_q_from_path_3rd(&path, 0, n)?;
1531            add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0))?;
1532
1533            let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1534            // Step 1. Topp2-RA
1535            let start = Instant::now();
1536            let options_ra0 = ReachSet2OptionsBuilder::new()
1537                .lp_feas_tol(1E-9)
1538                .a_cmp_abs_tol(1E-9)
1539                .a_cmp_rel_tol(1E-9)
1540                .build()?;
1541            let a_ra0 = topp2_ra(&topp2_problem, &options_ra0)?;
1542            let tc_ra0 = start.elapsed().as_secs_f64() * 1E3;
1543            let (tf_ra0, _) = s_to_t_topp2(s.as_slice(), &a_ra0, 0.0)?;
1544
1545            let objective = [CoppObjective::Time(1.0)];
1546            let copp3_problem =
1547                Copp3ProblemBuilder::new(&mut robot, &objective, 0, &a_ra0, (0.0, 0.0), (0.0, 0.0))
1548                    .build_with_linearization()?;
1549
1550            // Step 2. Test Copp3-SOCP
1551            let start = Instant::now();
1552            let profile_copp = {
1553                let mut settings = default_clarabel_settings();
1554                settings.tol_gap_rel = 1E-6;
1555                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1556                    .allow_almost_solved(true)
1557                    .build()?;
1558                let (result, solution) = match copp3_socp_expert(&copp3_problem, &options) {
1559                    Ok(res) => res,
1560                    Err(_) => {
1561                        crate::verbosity_log!(
1562                            crate::diag::Verbosity::Debug,
1563                            "Exp #{}: Clarabel solver failed in copp3_socp_expert!",
1564                            i_exp + 1
1565                        );
1566                        continue;
1567                    }
1568                };
1569                if let Some(result) = result {
1570                    result
1571                } else {
1572                    return Err(CoppError::ClarabelSolverStatus(
1573                        "copp3_socp".into(),
1574                        solution.status,
1575                    ));
1576                }
1577            };
1578            let tc_copp = start.elapsed().as_secs_f64() * 1E3;
1579            // Test time profile generation
1580            let (tf_copp, _) = s_to_t_topp3(s.as_slice(), profile_copp.as_parts(), 0.0)?;
1581
1582            // Step 3. Test Topp3-LP
1583            let start = Instant::now();
1584            let options_qp = ClarabelOptionsBuilder::new()
1585                .allow_almost_solved(true)
1586                .build()?;
1587            let profile_qp = topp3_socp(&copp3_problem.as_topp3_problem(), &options_qp)?;
1588            let tc_qp = start.elapsed().as_secs_f64() * 1E3;
1589            // Test time profile generation
1590            let (tf_qp, _) = s_to_t_topp3(s.as_slice(), profile_qp.as_parts(), 0.0)?;
1591
1592            let (obj_qp, _) = objective_value_copp3_opt(&copp3_problem, profile_qp.as_parts());
1593            let (obj_copp, _) = objective_value_copp3_opt(&copp3_problem, profile_copp.as_parts());
1594
1595            if flag_print_step {
1596                crate::verbosity_log!(
1597                    crate::diag::Verbosity::Summary,
1598                    "Exp #{}: tc_ra = {:.4} ms, tc_qp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_qp = {:.6}, tf_copp = {:.6}, obj_qp = {:.6}, obj_copp = {:.6}",
1599                    i_exp + 1,
1600                    tc_ra0,
1601                    tc_qp,
1602                    tc_copp,
1603                    tf_ra0,
1604                    tf_qp,
1605                    tf_copp,
1606                    obj_qp,
1607                    obj_copp
1608                );
1609            }
1610
1611            if (tf_copp - tf_qp).abs() > 1e-4 || (obj_copp - obj_qp).abs() > 1e-4 {
1612                crate::verbosity_log!(
1613                    crate::diag::Verbosity::Summary,
1614                    "omega = {omega:?}\nphi = {phi:?}"
1615                );
1616                crate::verbosity_log!(
1617                    crate::diag::Verbosity::Debug,
1618                    "COPP3 time optimality failed at Exp #{}! tf_copp - tf_qp = {}, obj_copp - obj_qp = {}",
1619                    i_exp + 1,
1620                    tf_copp - tf_qp,
1621                    obj_copp - obj_qp
1622                );
1623            }
1624
1625            tc_sum_ra += tc_ra0;
1626            tc_sum_qp += tc_qp;
1627            tc_sum_copp += tc_copp;
1628            tf_sum_ra += tf_ra0;
1629            tf_sum_qp += tf_qp;
1630            tf_sum_copp += tf_copp;
1631            obj_sum_qp += obj_qp;
1632            obj_sum_copp += obj_copp;
1633            succeed += 1;
1634        }
1635
1636        crate::verbosity_log!(
1637            crate::diag::Verbosity::Summary,
1638            "Average {n_exp} experiments (fail {}): tc_ra = {:.4} ms, tc_qp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_qp = {:.6}, tf_copp = {:.6}, obj_qp = {:.6}, obj_copp = {:.6}",
1639            n_exp - succeed,
1640            tc_sum_ra / succeed as f64,
1641            tc_sum_qp / succeed as f64,
1642            tc_sum_copp / succeed as f64,
1643            tf_sum_ra / succeed as f64,
1644            tf_sum_qp / succeed as f64,
1645            tf_sum_copp / succeed as f64,
1646            obj_sum_qp / succeed as f64,
1647            obj_sum_copp / succeed as f64
1648        );
1649
1650        Ok(())
1651    }
1652
1653    fn run_test_all_objectives_repeated(
1654        n_exp: usize,
1655        flag_print_step: bool,
1656    ) -> Result<(), CoppError> {
1657        let mut tc_sum_case0 = 0.0;
1658        let mut tc_sum_case1 = 0.0;
1659        let mut tc_sum_case2 = 0.0;
1660        let mut tc_sum_case3 = 0.0;
1661        let mut tc_sum_case4 = 0.0;
1662        let mut obj_sum_case0 = vec![0.0; 4];
1663        let mut obj_sum_case1 = vec![0.0; 4];
1664        let mut obj_sum_case2 = vec![0.0; 4];
1665        let mut obj_sum_case3 = vec![0.0; 4];
1666        let mut obj_sum_case4 = vec![0.0; 4];
1667
1668        let mut succeed = 0;
1669
1670        for i_exp in 0..n_exp {
1671            let n: usize = 1000;
1672            let dim = 7;
1673            let mut robot = Robot::with_capacity(dim, n);
1674
1675            let mut rng = rand::rng();
1676            let (s, path, omega, phi) =
1677                lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1678
1679            if flag_print_step {
1680                crate::verbosity_log!(
1681                    crate::diag::Verbosity::Summary,
1682                    "omega = {omega:?}\nphi = {phi:?}"
1683                );
1684            }
1685            robot
1686                .with_s(&s.as_view())?
1687                .with_q_from_path_3rd(&path, 0, n)?;
1688            add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0))?;
1689
1690            let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1691            // Step 1. Topp2-RA
1692            let options_ra0 = ReachSet2OptionsBuilder::new()
1693                .lp_feas_tol(1E-9)
1694                .a_cmp_abs_tol(1E-9)
1695                .a_cmp_rel_tol(1E-9)
1696                .build()?;
1697            let a_ra0 = topp2_ra(&topp2_problem, &options_ra0)?;
1698
1699            // Test different objectives in COPP2 optimization
1700            let objectives_test = [
1701                CoppObjective::Time(1.0),
1702                CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1703                CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1704                CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n - 1]),
1705            ];
1706
1707            // Case 0: Time only
1708            let obj_case0_src = [CoppObjective::Time(1.0)];
1709            let start = Instant::now();
1710            let (a_case0, b_case0, num_stationary) = {
1711                let copp3_problem = Copp3ProblemBuilder::new(
1712                    &mut robot,
1713                    &obj_case0_src,
1714                    0,
1715                    &a_ra0,
1716                    (0.0, 0.0),
1717                    (0.0, 0.0),
1718                )
1719                .build_with_linearization()?;
1720                let mut settings = default_clarabel_settings();
1721                settings.tol_gap_rel = 1E-6;
1722                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1723                    .allow_almost_solved(true)
1724                    .build()?;
1725                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1726                if let Some(result) = result {
1727                    result.into_parts()
1728                } else {
1729                    crate::verbosity_log!(
1730                        crate::diag::Verbosity::Debug,
1731                        "{:?}",
1732                        CoppError::ClarabelSolverStatus(
1733                            "copp3_socp (case 0)".into(),
1734                            solution.status,
1735                        )
1736                    );
1737                    continue;
1738                }
1739            };
1740            let tc_copp3_case0 = start.elapsed().as_secs_f64() * 1E3;
1741            let (_, obj_case0) = {
1742                let copp3_problem = Copp3ProblemBuilder::new(
1743                    &mut robot,
1744                    &objectives_test,
1745                    0,
1746                    &a_ra0,
1747                    (0.0, 0.0),
1748                    (0.0, 0.0),
1749                )
1750                .build_with_linearization()?;
1751                objective_value_copp3_opt(&copp3_problem, (&a_case0, &b_case0, num_stationary))
1752            };
1753
1754            // Case 1: Time and ThermalEnergy
1755            let obj_case1 = [
1756                CoppObjective::Time(1.0),
1757                CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1758            ];
1759            let start = Instant::now();
1760            let (a_case1, b_case1, num_stationary) = {
1761                let copp3_problem = Copp3ProblemBuilder::new(
1762                    &mut robot,
1763                    &obj_case1,
1764                    0,
1765                    &a_ra0,
1766                    (0.0, 0.0),
1767                    (0.0, 0.0),
1768                )
1769                .build_with_linearization()?;
1770                let mut settings = default_clarabel_settings();
1771                settings.tol_gap_rel = 1E-6;
1772                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1773                    .allow_almost_solved(true)
1774                    .build()?;
1775                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1776                if let Some(result) = result {
1777                    result.into_parts()
1778                } else {
1779                    crate::verbosity_log!(
1780                        crate::diag::Verbosity::Debug,
1781                        "{:?}",
1782                        CoppError::ClarabelSolverStatus(
1783                            "copp3_socp (case 1)".into(),
1784                            solution.status,
1785                        )
1786                    );
1787                    continue;
1788                }
1789            };
1790            let tc_copp3_case1 = start.elapsed().as_secs_f64() * 1E3;
1791            let (_, obj_case1) = {
1792                let copp3_problem = Copp3ProblemBuilder::new(
1793                    &mut robot,
1794                    &objectives_test,
1795                    0,
1796                    &a_ra0,
1797                    (0.0, 0.0),
1798                    (0.0, 0.0),
1799                )
1800                .build_with_linearization()?;
1801                objective_value_copp3_opt(&copp3_problem, (&a_case1, &b_case1, num_stationary))
1802            };
1803            if obj_case1[0] < obj_case0[0] - 1E-3 || obj_case1[1] - 1E-3 > obj_case0[1] {
1804                let (tf_case0, _) = s_to_t_topp2(s.as_slice(), &a_case0, 0.0)?;
1805                let (tf_case1, _) = s_to_t_topp2(s.as_slice(), &a_case1, 0.0)?;
1806                crate::verbosity_log!(
1807                    crate::diag::Verbosity::Summary,
1808                    "omega = {omega:?}\nphi = {phi:?}"
1809                );
1810                crate::verbosity_log!(
1811                    crate::diag::Verbosity::Summary,
1812                    "Case 0: obj_time = {}, obj_thermal_energy = {}, tf = {}",
1813                    obj_case0[0],
1814                    obj_case0[1],
1815                    tf_case0
1816                );
1817                crate::verbosity_log!(
1818                    crate::diag::Verbosity::Summary,
1819                    "Case 1: obj_time = {}, obj_thermal_energy = {}, tf = {}",
1820                    obj_case1[0],
1821                    obj_case1[1],
1822                    tf_case1
1823                );
1824                crate::verbosity_log!(
1825                    crate::diag::Verbosity::Summary,
1826                    "Interesting... Cases 0 and 1"
1827                );
1828            }
1829
1830            // Case 2: Time and More ThermalEnergy
1831            let obj_case2 = [
1832                CoppObjective::Time(1.0),
1833                CoppObjective::ThermalEnergy(10.0, &vec![1.0; dim]),
1834            ];
1835            let start = Instant::now();
1836            let (a_case2, b_case2, num_stationary) = {
1837                let copp3_problem = Copp3ProblemBuilder::new(
1838                    &mut robot,
1839                    &obj_case2,
1840                    0,
1841                    &a_ra0,
1842                    (0.0, 0.0),
1843                    (0.0, 0.0),
1844                )
1845                .build_with_linearization()?;
1846                let mut settings = default_clarabel_settings();
1847                settings.tol_gap_rel = 1E-6;
1848                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1849                    .allow_almost_solved(true)
1850                    .build()?;
1851                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1852                if let Some(result) = result {
1853                    result.into_parts()
1854                } else {
1855                    crate::verbosity_log!(
1856                        crate::diag::Verbosity::Debug,
1857                        "{:?}",
1858                        CoppError::ClarabelSolverStatus(
1859                            "copp3_socp (case 2)".into(),
1860                            solution.status,
1861                        )
1862                    );
1863                    continue;
1864                }
1865            };
1866            let tc_copp3_case2 = start.elapsed().as_secs_f64() * 1E3;
1867            let (_, obj_case2) = {
1868                let copp3_problem = Copp3ProblemBuilder::new(
1869                    &mut robot,
1870                    &objectives_test,
1871                    0,
1872                    &a_ra0,
1873                    (0.0, 0.0),
1874                    (0.0, 0.0),
1875                )
1876                .build_with_linearization()?;
1877                objective_value_copp3_opt(&copp3_problem, (&a_case2, &b_case2, num_stationary))
1878            };
1879            if obj_case2[0] < obj_case1[0] - 1E-3 || obj_case2[1] - 1E-3 > obj_case1[1] {
1880                crate::verbosity_log!(
1881                    crate::diag::Verbosity::Summary,
1882                    "omega = {omega:?}\nphi = {phi:?}"
1883                );
1884                crate::verbosity_log!(
1885                    crate::diag::Verbosity::Summary,
1886                    "Case 1: obj_time = {}, obj_thermal_energy = {}",
1887                    obj_case1[0],
1888                    obj_case1[1]
1889                );
1890                crate::verbosity_log!(
1891                    crate::diag::Verbosity::Summary,
1892                    "Case 2: obj_time = {}, obj_thermal_energy = {}",
1893                    obj_case2[0],
1894                    obj_case2[1]
1895                );
1896                crate::verbosity_log!(
1897                    crate::diag::Verbosity::Summary,
1898                    "Interesting... Cases 1 and 2"
1899                );
1900            }
1901
1902            // Case 3: Time and TotalVariationTorque
1903            let obj_case3 = [
1904                CoppObjective::Time(1.0),
1905                CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1906            ];
1907            let start = Instant::now();
1908            let (a_case3, b_case3, num_stationary) = {
1909                let copp3_problem = Copp3ProblemBuilder::new(
1910                    &mut robot,
1911                    &obj_case3,
1912                    0,
1913                    &a_ra0,
1914                    (0.0, 0.0),
1915                    (0.0, 0.0),
1916                )
1917                .build_with_linearization()?;
1918                let mut settings = default_clarabel_settings();
1919                settings.tol_gap_rel = 1E-6;
1920                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1921                    .allow_almost_solved(true)
1922                    .build()?;
1923                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1924                if let Some(result) = result {
1925                    result.into_parts()
1926                } else {
1927                    crate::verbosity_log!(
1928                        crate::diag::Verbosity::Debug,
1929                        "{:?}",
1930                        CoppError::ClarabelSolverStatus(
1931                            "copp3_socp (case 3)".into(),
1932                            solution.status,
1933                        )
1934                    );
1935                    continue;
1936                }
1937            };
1938            let tc_copp3_case3 = start.elapsed().as_secs_f64() * 1E3;
1939            let (_, obj_case3) = {
1940                let copp3_problem = Copp3ProblemBuilder::new(
1941                    &mut robot,
1942                    &objectives_test,
1943                    0,
1944                    &a_ra0,
1945                    (0.0, 0.0),
1946                    (0.0, 0.0),
1947                )
1948                .build_with_linearization()?;
1949                objective_value_copp3_opt(&copp3_problem, (&a_case3, &b_case3, num_stationary))
1950            };
1951            if obj_case3[1] < obj_case1[1] - 1E-3 || obj_case3[2] - 1E-3 > obj_case1[2] {
1952                crate::verbosity_log!(
1953                    crate::diag::Verbosity::Summary,
1954                    "omega = {omega:?}\nphi = {phi:?}"
1955                );
1956                crate::verbosity_log!(
1957                    crate::diag::Verbosity::Summary,
1958                    "Case 1: obj_time = {}, obj_thermal_energy = {}, obj_total_variation_torque = {}",
1959                    obj_case1[0],
1960                    obj_case1[1],
1961                    obj_case1[2]
1962                );
1963                crate::verbosity_log!(
1964                    crate::diag::Verbosity::Summary,
1965                    "Case 3: obj_time = {}, obj_thermal_energy = {}, obj_total_variation_torque = {}",
1966                    obj_case3[0],
1967                    obj_case3[1],
1968                    obj_case3[2]
1969                );
1970                crate::verbosity_log!(
1971                    crate::diag::Verbosity::Summary,
1972                    "Interesting... Cases 1 and 3"
1973                );
1974            }
1975
1976            // Case 4: Time and Linear
1977            let obj_case4 = [
1978                CoppObjective::Time(1.0),
1979                CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n]),
1980            ];
1981            let start = Instant::now();
1982            let (a_case4, b_case4, num_stationary) = {
1983                let copp3_problem = Copp3ProblemBuilder::new(
1984                    &mut robot,
1985                    &obj_case4,
1986                    0,
1987                    &a_ra0,
1988                    (0.0, 0.0),
1989                    (0.0, 0.0),
1990                )
1991                .build_with_linearization()?;
1992                let mut settings = default_clarabel_settings();
1993                settings.tol_gap_rel = 1E-6;
1994                let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1995                    .allow_almost_solved(true)
1996                    .build()?;
1997                let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1998                if let Some(result) = result {
1999                    result.into_parts()
2000                } else {
2001                    crate::verbosity_log!(
2002                        crate::diag::Verbosity::Debug,
2003                        "{:?}",
2004                        CoppError::ClarabelSolverStatus(
2005                            "copp3_socp (case 4)".into(),
2006                            solution.status,
2007                        )
2008                    );
2009                    continue;
2010                }
2011            };
2012            let tc_copp3_case4 = start.elapsed().as_secs_f64() * 1E3;
2013            let (_, obj_case4) = {
2014                let copp3_problem = Copp3ProblemBuilder::new(
2015                    &mut robot,
2016                    &objectives_test,
2017                    0,
2018                    &a_ra0,
2019                    (0.0, 0.0),
2020                    (0.0, 0.0),
2021                )
2022                .build_with_linearization()?;
2023                objective_value_copp3_opt(&copp3_problem, (&a_case4, &b_case4, num_stationary))
2024            };
2025            if obj_case4[1] < obj_case1[1] - 1E-3 || obj_case4[3] - 1E-3 > obj_case1[3] {
2026                crate::verbosity_log!(
2027                    crate::diag::Verbosity::Summary,
2028                    "omega = {omega:?}\nphi = {phi:?}"
2029                );
2030                crate::verbosity_log!(
2031                    crate::diag::Verbosity::Summary,
2032                    "Case 1: obj_time = {}, obj_thermal_energy = {}, obj_linear = {}",
2033                    obj_case1[0],
2034                    obj_case1[1],
2035                    obj_case1[3]
2036                );
2037                crate::verbosity_log!(
2038                    crate::diag::Verbosity::Summary,
2039                    "Case 4: obj_time = {}, obj_thermal_energy = {}, obj_linear = {}",
2040                    obj_case4[0],
2041                    obj_case4[1],
2042                    obj_case4[3]
2043                );
2044                crate::verbosity_log!(
2045                    crate::diag::Verbosity::Summary,
2046                    "Interesting... Cases 1 and 4"
2047                );
2048            }
2049            if obj_case4[2] < obj_case2[2] - 1E-3 || obj_case4[3] - 1E-3 > obj_case2[3] {
2050                crate::verbosity_log!(
2051                    crate::diag::Verbosity::Summary,
2052                    "omega = {omega:?}\nphi = {phi:?}"
2053                );
2054                crate::verbosity_log!(
2055                    crate::diag::Verbosity::Summary,
2056                    "Case 2: obj_time = {}, obj_total_variation_torque = {}, obj_linear = {}",
2057                    obj_case2[0],
2058                    obj_case2[2],
2059                    obj_case2[3]
2060                );
2061                crate::verbosity_log!(
2062                    crate::diag::Verbosity::Summary,
2063                    "Case 4: obj_time = {}, obj_total_variation_torque = {}, obj_linear = {}",
2064                    obj_case4[0],
2065                    obj_case4[2],
2066                    obj_case4[3]
2067                );
2068                crate::verbosity_log!(
2069                    crate::diag::Verbosity::Summary,
2070                    "Interesting... Cases 2 and 4"
2071                );
2072            }
2073
2074            succeed += 1;
2075
2076            if flag_print_step {
2077                crate::verbosity_log!(
2078                    crate::diag::Verbosity::Summary,
2079                    "Exp #{}:\n Case 0: tc={:.3}ms, obj={:?}\n Case 1: tc={:.3}ms, obj={:?}\n Case 2: tc={:.3}ms, obj={:?}\n Case 3: tc={:.3}ms, obj={:?}\n Case 4: tc={:.3}ms, obj={:?}",
2080                    i_exp + 1,
2081                    tc_copp3_case0,
2082                    obj_case0,
2083                    tc_copp3_case1,
2084                    obj_case1,
2085                    tc_copp3_case2,
2086                    obj_case2,
2087                    tc_copp3_case3,
2088                    obj_case3,
2089                    tc_copp3_case4,
2090                    obj_case4
2091                );
2092            }
2093
2094            tc_sum_case0 += tc_copp3_case0;
2095            tc_sum_case1 += tc_copp3_case1;
2096            tc_sum_case2 += tc_copp3_case2;
2097            tc_sum_case3 += tc_copp3_case3;
2098            tc_sum_case4 += tc_copp3_case4;
2099            for i in 0..obj_case0.len() {
2100                obj_sum_case0[i] += obj_case0[i];
2101                obj_sum_case1[i] += obj_case1[i];
2102                obj_sum_case2[i] += obj_case2[i];
2103                obj_sum_case3[i] += obj_case3[i];
2104                obj_sum_case4[i] += obj_case4[i];
2105            }
2106        }
2107
2108        for i in 0..4 {
2109            obj_sum_case0[i] /= n_exp as f64;
2110            obj_sum_case1[i] /= n_exp as f64;
2111            obj_sum_case2[i] /= n_exp as f64;
2112            obj_sum_case3[i] /= n_exp as f64;
2113            obj_sum_case4[i] /= n_exp as f64;
2114        }
2115
2116        crate::verbosity_log!(
2117            crate::diag::Verbosity::Summary,
2118            "Average {n_exp} experiments (fail {}):\n Case 0: tc={:.3}ms, obj={obj_sum_case0:?}\n Case 1: tc={:.3}ms, obj={obj_sum_case1:?}\n Case 2: tc={:.3}ms, obj={obj_sum_case2:?}\n Case 3: tc={:.3}ms, obj={obj_sum_case3:?}\n Case 4: tc={:.3}ms, obj={obj_sum_case4:?}",
2119            n_exp - succeed,
2120            tc_sum_case0 / succeed as f64,
2121            tc_sum_case1 / succeed as f64,
2122            tc_sum_case2 / succeed as f64,
2123            tc_sum_case3 / succeed as f64,
2124            tc_sum_case4 / succeed as f64
2125        );
2126
2127        Ok(())
2128    }
2129}